import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  ViewContainerRef,
  ElementRef,
  ChangeDetectorRef,
  OnDestroy,
  ViewChild,
  TemplateRef,
  Output,
  EventEmitter,
  forwardRef,
} from '@angular/core';
import { AnimationEvent } from '@angular/animations';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs';
import { TemplatePortal } from '@angular/cdk/portal';
import { takeUntil, tap, filter } from 'rxjs/operators';
import { formatDate } from '@angular/common';
import {
  OverlayService,
  I18NService,
  LvTimeFormat,
  LvHourMode,
  DateService,
  LvSizeLD,
  InputBoolean,
  LvConfig,
  isEmpty,
  showItem,
} from '../core/api';
import { ADJUST_POSITION_MAP, getPosition } from './time-picker-position';

@Component({
  selector: 'lv-time-picker',
  exportAs: 'lvTimePicker',
  templateUrl: './time-picker.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showItem],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof TimePickerComponent => TimePickerComponent),
      multi: true,
    },
  ],
  host: {
    class: 'lv-time-picker',
    '[class.lv-time-picker-size-large]': 'lvSize === "large"',
  },
})
export class TimePickerComponent implements ControlValueAccessor, OnInit, OnDestroy {
  @Input() lvStep: number[] = [1, 1, 1]; // 待实现：时分秒的变化步长

  @Input() lvSize: LvSizeLD = 'default';

  @Input() lvHourMode: LvHourMode = '24'; // 待实现：支持12小时制度或24小时制度,默认24小时制

  // 输入输出属性
  @Input() @InputBoolean() lvDisabled = false; // 是否禁用

  @Input() lvHeader: TemplateRef<void> = null;

  @Input() lvFooter: TemplateRef<void> = null;

  @Input() lvFormat: LvTimeFormat = 'HH:mm:ss'; // 时间格式

  @Input() lvDisabledHour: (hour: number) => boolean;

  @Output() lvOpenChange = new EventEmitter<boolean>(); // 切换面板回调函数

  @Input() lvPlaceholder = this._i18n.get('selectPlaceholder');

  @Input() lvDisabledMinute: (hour: number, minute: number) => boolean;

  @Input() @InputBoolean() lvReadonly = LvConfig.timePickerOptions.lvReadonly; // 输入框只读，只能通过下拉面板选择时间

  @Input() @InputBoolean() lvShowClear = LvConfig.timePickerOptions.lvShowClear; // 是否支持清除

  @ViewChild('timePanelTpl', { static: false }) _timePanelTpl: TemplateRef<any>;

  @Input() lvTimezoneOffset: number = LvConfig.timePickerOptions.lvTimezoneOffset; // 本地时间与格林威治标准时间 (GMT) 的分钟差

  @Input() lvDisabledSecond: (hour: number, minute: number, second: number) => boolean;

  @Input() @InputBoolean() lvNowAsBaseTime: boolean = LvConfig.timePickerOptions.lvNowAsBaseTime; // 是否以当前时间作为基准时间，如果为false则以零时零分零秒作为基准时间

  overlayRef;
  displayValue;
  hourContainer;
  minuteContainer;
  secondContainer;
  // 外部可访问变量
  public _value: Date;
  currentHours = '00';
  open: boolean = false;
  currentMinutes = '00';
  currentSeconds = '00';
  animationState = 'void';

  public _open$ = new Subject<boolean>();

  private _direction;
  // 内部变量
  private _destroy$ = new Subject<void>();
  private _refresh$ = new Subject<void>();
  private _openState$ = new Subject<boolean>();

  constructor(
    private _viewContainerRef: ViewContainerRef,
    private _overlayService: OverlayService,
    private _overlay: Overlay,
    private _elementRef: ElementRef,
    private _cdr: ChangeDetectorRef,
    private _i18n: I18NService,
    public dateService: DateService,
  ) {}

  get showHours(): boolean {
    return true;
  }

  get showMinutes(): boolean {
    return true;
  }

  get showSeconds(): boolean {
    return true;
  }

  get empty(): boolean {
    return isEmpty(this.displayValue);
  }

  get is12Hour(): boolean {
    return this.lvHourMode === '12';
  }

  setDisabledState?(): void {}

  setRefresh(): void {
    this._refresh$.next();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  // 关闭下拉
  close(): void {
    if (this._existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();

    this.close();
  }

  clear(): void {
    this.displayValue = '';
    this._value = null;
    this._onChange(this._value);
  }

  _existOverlayRef(): boolean {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  toggleOpen(): void {
    if (!this.lvDisabled) {
      this._open$.next((this.open = !this.open));
    }
    this._cdr.markForCheck();
  }

  // 订阅更新视图
  _subRefresh(): void {
    this._refresh$.pipe(takeUntil(this._destroy$)).subscribe((): void => this._cdr.markForCheck());
  }

  ngOnInit(): void {
    if (typeof this.lvFormat === 'function') {
      this.lvReadonly = true;
    }
    this._subOpen();
    this._subRefresh();
  }

  onFocus(e): void {
    if (!this.lvReadonly) {
      e.target.select();
    }
    this.open = true;
    this._open$.next(this.open);
    this._cdr.markForCheck();
  }

  beforeClose(): void {
    if (this._direction === 'up') {
      this.animationState = 'hideUp';
    } else {
      this.animationState = 'hideDown';
    }
    this._cdr.markForCheck();
  }

  onAnimationDone(event: AnimationEvent): void {
    const isUp = event.fromState === 'showUp' && event.toState === 'hideUp';
    const isDown = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (isUp || isDown) {
      this._openState$.next(false);
    }
  }

  onBlur(): void {
    if (!this.lvReadonly) {
      const value = this.displayValue;
      if (value === '' || value === null) {
        return;
      }

      const isValid = this.checkTimer(value);
      if (!isValid) {
        this.setTimer('');
      }
    }
  }

  timerChange(value): void {
    if (!value) {
      return;
    }
    this.formatTimer(value);

    const offsetDate = this.lvTimezoneOffset
      ? this.dateService.getTimezoneOffsetDate(this._value, this.lvTimezoneOffset)
      : this._value;
    this._onChange(offsetDate);
    this._cdr.markForCheck();
  }

  // 订阅切换面板的流
  _subOpen(): void {
    this._open$
      .pipe(
        tap((res: boolean): void => {
          if (!res) {
            this.close();
            this.setRefresh();
          }
        }), // 关闭
        filter((res): any => res), // 打开
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this._openPanelOverlay();
      });
  }

  afterOpen(overlayInstance): void {
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this._cdr.markForCheck();
    });
  }

  writeValue(value: any): void {
    if (value === null || value === undefined) {
      this.displayValue = '';
      this._value = null;
    } else if (value instanceof Date) {
      const offsetDate = this.lvTimezoneOffset
        ? this.dateService.getTimezoneOffsetDate(value, this.lvTimezoneOffset, true)
        : value;
      this.formatTimer(offsetDate);
    } else {
      // eslint-disable-next-line no-console
      console.error('The format of time is incorrect.');
    }
    this._cdr.markForCheck();
  }

  checkTimer(value): boolean {
    const regMap = {
      ['hh:mm:ss a']: /^(0?[0-9]|1[0-2]):([0-5][0-9]):([0-5][0-9]) (am|pm)$/,
      ['hh:mm:ss A']: /^(0?[0-9]|1[0-2]):([0-5][0-9]):([0-5][0-9]) (AM|PM)$/,
      ['hh:mm a']: /^(0?[0-9]|1[0-2]):([0-5][0-9]) (am|pm)$/,
      ['hh:mm A']: /^(0?[0-9]|1[0-2]):([0-5][0-9]) (AM|PM)$/,
      ['HH:mm:ss']: /^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/,
      ['HH:mm']: /^([0-1]?[0-9]|2[0-3]):([0-5][0-9])$/,
      ['mm:ss']: /^[0-5][0-9]:[0-5][0-9]$/,
    };
    if (regMap[this.lvFormat].exec(value) === null) {
      return false;
    }
    return true;
  }

  formatTimer(value): void {
    this._value = value;

    const _h = this._value.getHours();
    const _m = this._value.getMinutes();
    const _s = this._value.getSeconds();
    this.currentHours = _h < 10 ? `0${_h}` : _h.toString();
    this.currentMinutes = _m < 10 ? `0${_m}` : _m.toString();
    this.currentSeconds = _s < 10 ? `0${_s}` : _s.toString();

    this.lvFormat === 'HH:mm:ss' &&
      (this.displayValue = `${this.currentHours}:${this.currentMinutes}:${this.currentSeconds}`);
    this.lvFormat === 'HH:mm' && (this.displayValue = `${this.currentHours}:${this.currentMinutes}`);
    this.lvFormat === 'mm:ss' && (this.displayValue = `${this.currentMinutes}:${this.currentSeconds}`);
    if (['hh:mm:ss A', 'hh:mm:ss a', 'hh:mm A', 'hh:mm a'].includes(this.lvFormat)) {
      const caseFlag = this.lvFormat.split(' ')[1];
      const date = new Date();
      date.setHours(Number(this.currentHours));
      date.setMinutes(Number(this.currentMinutes));
      date.setSeconds(Number(this.currentSeconds));
      const displayValue = `${formatDate(date, this.lvFormat.toLowerCase(), 'en-us')}`;
      this.displayValue = caseFlag === 'A' ? displayValue : displayValue.toLowerCase();
    }
  }

  // 打开下拉panel overlay
  _openPanelOverlay(): void {
    if (!this._existOverlayRef()) {
      const config = new OverlayConfig({
        hasBackdrop: false,
        scrollStrategy: this._overlay.scrollStrategies.block(),
        positionStrategy: this._overlay
          .position()
          .flexibleConnectedTo(this._elementRef)
          .withPositions(ADJUST_POSITION_MAP),
      });
      const extendConfig = {
        afterOpen: (overlayRef): void => {
          this.afterOpen(overlayRef);
          this.lvOpenChange.emit(this.open);
        },
        beforeClose: (): Promise<void> =>
          new Promise<void>((resolve): void => {
            this.beforeClose();
            const closesubscribe = this._openState$.subscribe((open): void => {
              if (!open) {
                closesubscribe.unsubscribe();
                resolve();
              }
            });
          }),
        afterClose: (): void => {
          this.open = false;
          this._cdr.markForCheck();
          this.lvOpenChange.emit(this.open);
        },
      };

      const portal = new TemplatePortal(this._timePanelTpl, this._viewContainerRef);
      this.overlayRef = this._overlayService.open(portal, config, extendConfig);
      this.setRefresh();
    }
  }

  setTimer(value): void {
    this.displayValue = value;

    const isValid = this.checkTimer(value);
    if (isValid) {
      const [timeStr, flag] = value.split(' ');
      const arr = timeStr.split(':');
      if (this.lvFormat === 'HH:mm:ss') {
        this.currentHours = arr[0];
        this.currentMinutes = arr[1];
        this.currentSeconds = arr[2];
      }
      if (this.lvFormat === 'HH:mm') {
        this.currentHours = arr[0];
        this.currentMinutes = arr[1];
        this.currentSeconds = '00';
      }
      if (this.lvFormat === 'mm:ss') {
        this.currentHours = '00';
        this.currentMinutes = arr[0];
        this.currentSeconds = arr[1];
      }
      if (['hh:mm A', 'hh:mm a', 'hh:mm:ss a', 'hh:mm:ss A'].includes(this.lvFormat)) {
        if (flag.toLowerCase() === 'pm') {
          this.currentHours = String(Number(arr[0]) + 12);
        } else {
          this.currentHours = arr[0];
        }
        this.currentMinutes = arr[1];
        this.currentSeconds = ['hh:mm:ss a', 'hh:mm:ss A'].includes(this.lvFormat) ? arr[2] : '00';
      }

      const date = new Date();

      date.setHours(Number(this.currentHours));
      date.setMinutes(Number(this.currentMinutes));
      date.setSeconds(Number(this.currentSeconds));

      this._value = date;
      const offsetDate = this.lvTimezoneOffset
        ? this.dateService.getTimezoneOffsetDate(this._value, this.lvTimezoneOffset)
        : this._value;
      this._onChange(offsetDate);
    } else {
      this.currentHours = '00';
      this.currentMinutes = '00';
      this.currentSeconds = '00';
      this._value = null;
      this._onChange(this._value);
    }
  }

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};
}
